<template><div><p>本文档最新版为 <a href="https://learnku.com/docs/laravel/10.x" target="_blank" rel="noopener noreferrer">10.x</a>，旧版本可能放弃维护，推荐阅读最新版！</p>
<h2 id="csrf-保护" tabindex="-1"><a class="header-anchor" href="#csrf-保护"><span>CSRF 保护</span></a></h2>
<ul>
<li><a href="#csrf-introduction">简介</a></li>
<li><a href="#preventing-csrf-requests">阻止 CSRF 请求</a>
<ul>
<li><a href="#csrf-excluding-uris">排除 URIs</a></li>
</ul>
</li>
<li><a href="#csrf-x-csrf-token">X-CSRF-TOKEN</a></li>
<li><a href="#csrf-x-xsrf-token">X-XSRF-TOKEN</a></li>
</ul>
<h2 id="简介" tabindex="-1"><a class="header-anchor" href="#简介"><span>简介</span></a></h2>
<p>跨站点请求伪造是一种恶意攻击类型，利用这种手段，代表经过身份验证的用户执行未经授权的命令。值得庆幸的是，Laravel 可以轻松保护你的应用程序免受<a href="https://en.wikipedia.org/wiki/Cross-site_request_forgery" target="_blank" rel="noopener noreferrer">跨站点请求伪造</a>（CSRF）攻击。</p>
<h4 id="漏洞的解释" tabindex="-1"><a class="header-anchor" href="#漏洞的解释"><span>漏洞的解释</span></a></h4>
<p>如果你不熟悉跨站点请求伪造，我们讨论一个利用此漏洞的示例。假设你的应用程序有一个 <code v-pre>/user/email</code> 路由，它接受 <code v-pre>POST</code> 请求来更改经过身份验证用户的电子邮件地址。最有可能的情况是，此路由希望 <code v-pre>email</code> 输入字段包含用户希望开始使用的电子邮件地址。</p>
<p>没有 CSRF 保护，恶意网站可能会创建一个 HTML 表单，指向你的应用程序 <code v-pre>/user/email</code> 路由，并提交恶意用户自己的电子邮件地址：</p>
<div class="language-blade line-numbers-mode" data-highlighter="prismjs" data-ext="blade" data-title="blade"><pre v-pre class="language-blade"><code><span class="line">&lt;form action=&quot;https://your-application.com/user/email&quot; method=&quot;POST&quot;&gt;</span>
<span class="line">    &lt;input type=&quot;email&quot; value=&quot;malicious-email@example.com&quot;&gt;</span>
<span class="line">&lt;/form&gt;</span>
<span class="line"></span>
<span class="line">&lt;script&gt;</span>
<span class="line">    document.forms[0].submit();</span>
<span class="line">&lt;/script&gt;</span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>如果恶意网站在页面加载时自动提交了表单，则恶意用户只需要诱使你的应用程序的一个毫无戒心的用户访问他们的网站，他们的电子邮件地址就会在你的应用程序中更改。</p>
<p>为了防止这种漏洞，我们需要检查每一个传入的 <code v-pre>POST</code>，<code v-pre>PUT</code>，<code v-pre>PATCH</code> 或 <code v-pre>DELETE</code> 请求以获取恶意应用程序无法访问的秘密会话值。</p>
<h2 id="阻止-csrf-请求" tabindex="-1"><a class="header-anchor" href="#阻止-csrf-请求"><span>阻止 CSRF 请求</span></a></h2>
<p>Laravel 会自动为应用程序管理的每个活动 <a href="https://learnku.com/docs/laravel/11.x/session" target="_blank" rel="noopener noreferrer">用户会话</a> 生成一个 CSRF 「令牌」。此令牌用于验证经过身份验证的用户是否是实际向应用程序发出请求的人。由于此令牌存储在用户的会话中，并且每次重新生成会话时都会发生变化，因此恶意应用程序无法访问它。</p>
<p>当前会话的 CSRF 令牌可以通过请求的会话或 <code v-pre>csrf_token</code> 辅助函数来访问：</p>
<div class="language-php line-numbers-mode" data-highlighter="prismjs" data-ext="php" data-title="php"><pre v-pre class="language-php"><code><span class="line"><span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Http<span class="token punctuation">\</span>Request</span><span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line"><span class="token class-name static-context">Route</span><span class="token operator">::</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'/token'</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token class-name type-declaration">Request</span> <span class="token variable">$request</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">    <span class="token variable">$token</span> <span class="token operator">=</span> <span class="token variable">$request</span><span class="token operator">-></span><span class="token function">session</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">token</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line">    <span class="token variable">$token</span> <span class="token operator">=</span> <span class="token function">csrf_token</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line">    <span class="token comment">// ...</span></span>
<span class="line"><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>每当你在应用程序中定义 <code v-pre>POST</code> 、 <code v-pre>PUT</code> 、 <code v-pre>PATCH</code> 或 <code v-pre>DELETE</code> HTML 表单时，都应该在表单中包含一个隐藏的 CSRF <code v-pre>_token</code> 字段，以便 CSRF 保护中间件可以验证请求。为了方便起见，你可以使用 <code v-pre>@csrf</code> Blade 指令来生成隐藏令牌输入字段：</p>
<div class="language-blade line-numbers-mode" data-highlighter="prismjs" data-ext="blade" data-title="blade"><pre v-pre class="language-blade"><code><span class="line">&lt;form method=&quot;POST&quot; action=&quot;/profile&quot;&gt;</span>
<span class="line">    @csrf</span>
<span class="line"></span>
<span class="line">    &lt;!-- 相当于... --&gt;</span>
<span class="line">    &lt;input type=&quot;hidden&quot; name=&quot;_token&quot; value=&quot;{{ csrf_token() }}&quot; /&gt;</span>
<span class="line">&lt;/form&gt;</span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>默认情况下包含在 <code v-pre>web</code> 中间件组中的 <code v-pre>Illuminate\Foundation\Http\Middleware\ValidateCsrfToken</code> <a href="https://learnku.com/docs/laravel/11.x/middleware" target="_blank" rel="noopener noreferrer">中间件</a> 将自动验证请求输入中的令牌是否与会话中存储的令牌匹配。当这两个令牌匹配时，我们就知道经过身份验证的用户是发起请求的用户。</p>
<h3 id="csrf-tokens-spas" tabindex="-1"><a class="header-anchor" href="#csrf-tokens-spas"><span>CSRF Tokens &amp; SPAs</span></a></h3>
<p>如果你正在构建使用 Laravel 作为 API 后端的 SPA，则应查阅 <a href="https://learnku.com/docs/laravel/11.x/sanctum" target="_blank" rel="noopener noreferrer">Laravel Sanctum</a> 文档，了解有关使用 API 进行身份验证和防范 CSRF 漏洞的信息。</p>
<h3 id="从-csrf-保护中排除-uri" tabindex="-1"><a class="header-anchor" href="#从-csrf-保护中排除-uri"><span>从 CSRF 保护中排除 URI</span></a></h3>
<p>有时你可能希望从 CSRF 保护中排除一组 URIs。例如，如果你使用 <a href="https://stripe.com/" target="_blank" rel="noopener noreferrer">Stripe</a> 处理付款并使用他们的 webhook 系统，则需要将你的 Stripe webhook 处理程序路由从 CSRF 保护中排除，因为 Stripe 不会知道要向你的路由发送什么 CSRF 令牌。</p>
<p>通常，你应该将这类路由放置在 Laravel 应用于 <code v-pre>routes/web.php</code> 文件中所有路由的 <code v-pre>web</code> 中间件组之外。但是，你也可以通过将特定路由的URI提供给应用程序的 <code v-pre>bootstrap/app.php</code> 文件中的 <code v-pre>validateCsrfTokens</code> 方法来排除这些路由：</p>
<div class="language-php line-numbers-mode" data-highlighter="prismjs" data-ext="php" data-title="php"><pre v-pre class="language-php"><code><span class="line"><span class="token operator">-></span><span class="token function">withMiddleware</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token class-name type-declaration">Middleware</span> <span class="token variable">$middleware</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">    <span class="token variable">$middleware</span><span class="token operator">-></span><span class="token function">validateCsrfTokens</span><span class="token punctuation">(</span><span class="token argument-name">except</span><span class="token punctuation">:</span> <span class="token punctuation">[</span></span>
<span class="line">        <span class="token string single-quoted-string">'stripe/*'</span><span class="token punctuation">,</span></span>
<span class="line">        <span class="token string single-quoted-string">'http://example.com/foo/bar'</span><span class="token punctuation">,</span></span>
<span class="line">        <span class="token string single-quoted-string">'http://example.com/foo/*'</span><span class="token punctuation">,</span></span>
<span class="line">    <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span><span class="token punctuation">)</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><blockquote>
<p><strong>技巧</strong><br>
为方便起见，<a href="https://learnku.com/docs/laravel/11.x/testing" target="_blank" rel="noopener noreferrer">运行测试</a>时自动禁用所有路由的 CSRF 中间件。</p>
</blockquote>
<h2 id="x-csrf-token" tabindex="-1"><a class="header-anchor" href="#x-csrf-token"><span>X-CSRF-TOKEN</span></a></h2>
<p>除了检查POST参数中的CSRF令牌外，<code v-pre>Illuminate\Foundation\Http\Middleware\ValidateCsrfToken</code> 中间件默认包含在 <code v-pre>web</code> 中间件组中，它还会检查 <code v-pre>X-CSRF-TOKEN</code> 请求头。例如，你可以将令牌存储在 HTML 的 <code v-pre>meta</code> 标签中：</p>
<div class="language-blade line-numbers-mode" data-highlighter="prismjs" data-ext="blade" data-title="blade"><pre v-pre class="language-blade"><code><span class="line">&lt;meta name=&quot;csrf-token&quot; content=&quot;{{ csrf_token() }}&quot;&gt;</span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>然后，你可以指示 jQuery 之类的库自动将令牌添加到所有请求标头。 这为使用传统 JavaScript 技术的基于 AJAX 的应用程序提供了简单、方便的 CSRF 保护：</p>
<div class="language-javascript line-numbers-mode" data-highlighter="prismjs" data-ext="js" data-title="js"><pre v-pre class="language-javascript"><code><span class="line">$<span class="token punctuation">.</span><span class="token function">ajaxSetup</span><span class="token punctuation">(</span><span class="token punctuation">{</span></span>
<span class="line">    <span class="token literal-property property">headers</span><span class="token operator">:</span> <span class="token punctuation">{</span></span>
<span class="line">        <span class="token string-property property">'X-CSRF-TOKEN'</span><span class="token operator">:</span> <span class="token function">$</span><span class="token punctuation">(</span><span class="token string">'meta[name="csrf-token"]'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">attr</span><span class="token punctuation">(</span><span class="token string">'content'</span><span class="token punctuation">)</span></span>
<span class="line">    <span class="token punctuation">}</span></span>
<span class="line"><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2 id="x-xsrf-token" tabindex="-1"><a class="header-anchor" href="#x-xsrf-token"><span>X-XSRF-TOKEN</span></a></h2>
<p>Laravel 将当前 CSRF 令牌存储在加密的 <code v-pre>XSRF-TOKEN</code> cookie 中，该 cookie 包含在框架生成的每个响应中。你可以使用 cookie 值设置 <code v-pre>X-XSRF-TOKEN</code> 请求标头。</p>
<p>由于一些 JavaScript 框架和库（如 Angular 和 Axios ）会自动将其值放置在同一源请求的 <code v-pre>X-XSRF-TOKEN</code> 标头中，因此发送此 cookie 主要是为了方便开发人员。</p>
<blockquote>
<p><strong>技巧</strong><br>
默认情况下，<code v-pre>resources/js/bootstrap.js</code> 文件包含 Axios HTTP 库，它会自动为你发送 <code v-pre>X-XSRF-TOKEN</code> 标头。</p>
</blockquote>
<blockquote>
<p>本译文仅用于学习和交流目的，转载请务必注明文章译者、出处、和本文链接<br>
我们的翻译工作遵照 <a href="https://learnku.com/docs/guide/cc4.0/6589" target="_blank" rel="noopener noreferrer">CC 协议</a>，如果我们的工作有侵犯到您的权益，请及时联系我们。</p>
</blockquote>
<hr>
<blockquote>
<p>原文地址：<a href="https://learnku.com/docs/laravel/11.x/csrfmd/16659" target="_blank" rel="noopener noreferrer">https://learnku.com/docs/laravel/11.x/cs...</a></p>
<p>译文地址：<a href="https://learnku.com/docs/laravel/11.x/csrfmd/16659" target="_blank" rel="noopener noreferrer">https://learnku.com/docs/laravel/11.x/cs...</a></p>
</blockquote>
</div></template>


